#include "Epoller.h"
#include "Eventloop.h"
#include "IOchannelProxy.h"
#include "log/Logger.h"
#include "utility/assertion.h"
#include "utility/cast.h"
#include "utility/feature.h"
#include "wrap/epoll.h"
#include "wrap/unistd.h"

#include <sys/epoll.h>  // for struct epoll_event

namespace _ {

static std::string epoll_operation_str(int operation) {
    switch(operation) {
    case Wrap::EPOLL_CTL_ADD_:
        return "ADD";
    case Wrap::EPOLL_CTL_DEL_:
        return "DEL";
    case Wrap::EPOLL_CTL_MOD_:
        return "MOD";
    default:
        return "Unkown Flag: " + std::to_string(operation);
    }
}

}

Epoller::Epoller(Eventloop &loop):
    Poller{loop},
    _fdMap{},
    _events{1 << 5},
    _pollFD{Wrap::epoll_create1_(Wrap::EPOLL_CLOEXEC_)}
{
    LOG_VERBOSE << "create fd=" << _pollFD;
    if (_pollFD < 0) {
        LOG_SYSFATAL << "create fd";
    }
}

Epoller::~Epoller() {
    Wrap::close_(_pollFD);
}

bool Epoller::has(const IOchannelProxy &channel) const {
    return _is_in_channels(channel.fd()) && _fdMap.at(channel.fd()).channel.value().get() == channel;
}

Utility::timepoint Epoller::poll(int timeout, IOchannelProxyRefs &activeChannels)  {
    const auto nfds = Wrap::epoll_wait_(
        _pollFD, _events.data(), static_cast<int>(_events.size()), timeout);
    const auto now = Poller::_polling_nfds(nfds, activeChannels);
    if (nfds > 0 && Cast::EQ(_events.size(), nfds)) {
        _events.resize(_events.size() * 2);
    }
    return now;
}

void Epoller::remove(IOchannelProxy &channel) {
    _loop.ASSERT_IN_THIS_THREAD();
    LOG_VERBOSE << "fd=" << channel.fd() << ",name=" << channel.name();
    assert(has(channel));
    if (_fdMap.at(channel.fd()).alive) {
        _update(channel, Wrap::EPOLL_CTL_DEL_);
    }
    MAYBE_UNUSED const auto n = _fdMap.erase(channel.fd());
    assert(n == 1);
}

void Epoller::update(IOchannelProxy &channel) {
    _loop.ASSERT_IN_THIS_THREAD();
    LOG_VERBOSE << "fd=" << channel.fd() << ",name=" << channel.name()
                << ",events=" << channel.listen_events_string();
    if (_fdMap.find(channel.fd()) != _fdMap.cend()) {
        _change(channel);
        return;
    }
    assert(!_is_in_channels(channel.fd()));
    _update(channel, Wrap::EPOLL_CTL_ADD_);
    MAYBE_UNUSED const auto inserted =
        _fdMap.insert({channel.fd(), _Helper{channel}}).second;
    assert(inserted);
}


void Epoller::_change(const IOchannelProxy &channel) {
    assert(has(channel));
    if (!_fdMap.at(channel.fd()).alive) {
        _update(channel, Wrap::EPOLL_CTL_ADD_);
        _fdMap.at(channel.fd()).alive = true;
        return;
    }
    if (channel.is_idling()) {
        _update(channel, Wrap::EPOLL_CTL_DEL_);
        _fdMap.at(channel.fd()).alive = false;
        return;
    }
    _update(channel, Wrap::EPOLL_CTL_MOD_);
}

void Epoller::_fill_active_channels(int nfds, IOchannelProxyRefs &activeChannels) const {
    assert(Cast::GE(_events.size(), nfds));
    for (std::size_t i = 0; Cast::LT(i, nfds); i++) {
        MAYBE_UNUSED const auto iter = _fdMap.find(_events.at(i).data.fd);
        assert(iter != _fdMap.cend());
        assert(iter->second.channel.has_value());
        assert(has(iter->second.channel.value().get()));
        const auto &channel = _fdMap.at(_events.at(i).data.fd).channel.value();
        channel.get().set_return_events(static_cast<int>(_events.at(i).events));
        activeChannels.emplace_back(channel);
    }
}

bool Epoller::_is_in_channels(int fd) const {
    const auto iter = _fdMap.find(fd);
    return iter != _fdMap.cend() && iter->second.channel.has_value();
}

void Epoller::_update(const IOchannelProxy &channel, int operation) const {
    struct epoll_event event {};
    event.events = static_cast<uint32_t>(channel.listen_events());
    event.data.fd = channel.fd();
    LOG_VERBOSE << "epoll_ctl_operation=" << _::epoll_operation_str(operation)
                << ",fd=" << channel.fd() << ",name=" << channel.name()
                << ",event{" << channel.listen_events_string() << "}";
    if (Wrap::epoll_ctl_(_pollFD, operation, channel.fd(), &event) == 0) {
        return;
    }
    if (operation == Wrap::EPOLL_CTL_DEL_) {
        LOG_SYSERROR << "epoll_ctl_operaiton="
                     << _::epoll_operation_str(operation)
                     << ",fd=" << channel.fd() << ",name=" << channel.name();
        return;
    }
    LOG_SYSFATAL << "epoll_ctl_operation=" << _::epoll_operation_str(operation)
                 << ",fd=" << channel.fd() << ",name=" << channel.name();
}
